/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* SCRMGR front end to find(1) */

#include <stdio.h>
#include <getopt.h>
#include <setjmp.h>
#include <sys/types.h>		/* for stat */
#include <sys/stat.h>		/* for stat */
#include <sys/time.h>
#include <unistd.h>
#include "smlib.h"
#include "smio.h"
#include "backup.h"
#include "smfind.def"

CHAR *smfind_rcsid(void) {
    return("$Id: smfind.c,v 1.7 2008/02/20 13:20:32 bhepple Exp $");
}

static struct {
    CHAR	*rev_string;
} global;

jmp_buf		NoMoreHeap;

static CHAR *usage_msg[] = {
"Provide an easy to use front end to find(1) for finding files.",
"",
NULL
};

#if !READ_DAT_AT_RUNTIME
static CHAR *smfind_dat[] = {
#include "smfind.dat"
NULL,
};
#endif

static void usage()
{
    CHAR **c;
    
    fprintf(stderr, "%s of %s.\n", global.rev_string, sm_program_name);
    fprintf(stderr, "Usage: %s\n", sm_program_name);
    
    c = usage_msg;
    while (*c) {
        fputs(*c, stderr);
        fputc('\n', stderr);
        c++;
    }
    exit(2);
}

void sm_error_exit ARG1(CHAR *, msg)
{
    sm_term();
    puts(msg);
    exit(1);
}

void re_fail ARG2(char *, msg, char, op)
{
    sm_message(msg);
}

/* Exported to the SCRMGR package */
f_ptr sm_get_field_trigger ARG1(SM_FIELD, field)
{
    return(NULL);
}

CALLBACK(do_save_list)
{
    CHAR	*filename;
    FILE	*file;

    /* Always entered after sm_prompt */
    if (command == SM_YES_C) {
	filename = sm_get_prompt_value();

	if ((file = fopen(filename, "r")) != NULL) {
	    CHAR	*buf = alloca(100);

	    fclose(file);
	    sprintf(buf, "\"%s\" already exists. Overwrite it?", filename);
	    if (sm_confirm(buf) != SM_YES_C)
		return(SM_QUIT_C);
	}
    } else
	return(command);

    smdb_write_file(filename, hit_scr, 0);

    return(SM_POPCALLBACK);
}

CALLBACK(do_exec_command)
{
    CHAR	*buf = alloca(SM_MAXREC), *command_line, *filename;
    int		confirm, fconfirm, all_files;

    /* Always entered after sm_prompt */
    if (command == SM_YES_C) {
	command_line = sm_trim(sm_get_prompt_value());

	all_files = 1;
	confirm = 0;
	if (smdb_num_records(hit_scr) > 1) {
	    sprintf(buf, "Run \"%s\" on all files?", command_line);
	    all_files = sm_confirm(buf);
	    if (all_files == SM_CANCEL_C)
		return(SM_POPCALLBACK);
	    all_files = (all_files == SM_YES_C)? 1: 0;
	    if (all_files) {
		confirm = sm_confirm("Confirm before every file?");
		if (confirm == SM_CANCEL_C)
		    return(SM_POPCALLBACK);
		confirm = (confirm == SM_YES_C? 1: 0);
		filename = smdb_seek(hit_scr, (INDEX) 0);
	    }
	}

	filename = smdb_get_current(hit_scr);

	while (filename && *filename) {
	    if (confirm) {
		sprintf(buf, "Do \"%s %s\"?", command_line, filename);
		fconfirm = sm_confirm(buf);
		if (fconfirm == SM_CANCEL_C)
		    return(SM_POPCALLBACK);
		fconfirm = (fconfirm == SM_YES_C? 1: 0);
	    }
	    if (!confirm || fconfirm) {
		sprintf(buf, "%s %s",  command_line, filename);
		smio_system(buf, 0);
	    }
	    if (!all_files)
		break;
	    filename = smdb_read_next(hit_scr);
	}
    } 
    if ((command == SM_CANCEL_C) ||
	(command == SM_YES_C) ||
	(command == SM_NO_C))
	command = SM_POPCALLBACK;
    return(command);
}

CALLBACK(do_hit)
{
    int		retval = 0, confirm, fconfirm, all_files;
    CHAR	*line = alloca(SM_MAXREC), *filename, *p;
    CHAR	pwd[MAXPATHLEN + 1];

    switch (command) {
    case VIEW_FILE:
    case EDIT_FILE:
	if (smdb_num_records(hit_scr) > 0) {
	    if (command == VIEW_FILE) {
		p = getenv("PAGER");
		if (p && *p)
		    strcpy(line, p);
		else
		    strcpy(line, "more");
	    } else {
		p = getenv("EDITOR");
		if (p && *p)
		    strcpy(line, p);
		else
		    strcpy(line, "vi");
	    }
	    
	    strcat(line, " ");
	    filename = smdb_get_current(hit_scr);
	    strcat(line, filename);
	    smio_system(line, 0);
	}
	break;

    case TOUCH_FILE:
	if (smdb_num_records(hit_scr) > 0) {
	    all_files = 0;
	    confirm = 0;
	    if (smdb_num_records(hit_scr) > 1) {
		all_files = sm_confirm("Touch all files?");
		if (all_files == SM_CANCEL_C)
		    break;
		all_files = (all_files == SM_YES_C)? 1: 0;
		smdb_seek(hit_scr, (INDEX) 0);
		if (all_files) {
		    confirm = sm_confirm("Confirm before every file?");
		    if (confirm == SM_CANCEL_C) 
			break;
		    confirm = (confirm == SM_YES_C? 1: 0);
		}
	    }
	    filename = smdb_get_current(hit_scr);
	    while (filename && *filename) {
		fconfirm = 1;
		if (confirm) {
		    sprintf(line, "Touch \"%s\"?", filename);
		    fconfirm = sm_confirm(line);
		    if (fconfirm == SM_CANCEL_C)
			break;
		    fconfirm = (fconfirm == SM_YES_C)? 1: 0;
		}
		if (!confirm || fconfirm)
		    utimes(filename, NULL);
		if (all_files)
		    filename = smdb_read_next(hit_scr);
		else
		    break;
	    }
	}
	break;

    case REMOVE_FILE:
	if (smdb_num_records(hit_scr) > 0) {
	    sprintf(line, "Really remove \"%s\"?", smdb_get_current(hit_scr));
	    if (sm_confirm(line) == SM_YES_C) {
		unlink(smdb_get_current(hit_scr));
		smdb_delete_rec(hit_scr);
	    }
	}
	break;

    case REMOVE_ALL:
	smdb_seek(hit_scr, (INDEX) 0);
	if (smdb_num_records(hit_scr) > 0) {
	    confirm = 1;
	    if (smdb_num_records(hit_scr) > 1) {
		confirm = sm_confirm("Confirm before every file?");
		if (confirm == SM_CANCEL_C) 
		    break;
		confirm = (confirm == SM_YES_C? 1: 0);
	    }
	    while (smdb_get_current(hit_scr)) {
		if (confirm) {
		    sprintf(line, "Really remove file \"%s\"?", 
			    smdb_get_current(hit_scr));
		    fconfirm = sm_confirm(line);
		    if (fconfirm == SM_CANCEL_C)
			break;
		    fconfirm = (fconfirm == SM_YES_C? 1: 0);
		}
		if (!confirm || fconfirm) {
		    unlink(smdb_get_current(hit_scr));
		    smdb_delete_rec(hit_scr);
		} else
		    smdb_read_next(hit_scr);
	    }
	    smdb_seek(hit_scr, (INDEX) 0);
	}
	break;

    case EXEC_COMMAND:
	if (smdb_num_records(hit_scr) > 0) {
	    sm_setup_edit(SM_BASE_WINDOW,
			  hit_scr,
			  SM_QUIT_C,
			  NULL,
			  hit_skey,
			  NULL);
	    sm_add_callback(do_exec_command, NULL);
	    GETCWD(pwd, MAXPATHLEN);
	    sm_setup_prompt("Enter command to execute on all these files", 
			    pwd, NULL, NULL);
	}
	return(0);

    case SAVE_LIST:
	if (smdb_num_records(hit_scr) > 0) {
	    sm_setup_edit(SM_BASE_WINDOW,
			  hit_scr,
			  SM_QUIT_C,
			  NULL,
			  hit_skey,
			  NULL);
	    sm_add_callback(do_save_list, NULL);
	    GETCWD(pwd, MAXPATHLEN);
	    sm_setup_prompt("Enter filename to save list", pwd, NULL, NULL);
	}
	return(0);

    case SM_QUIT_C:
    case SM_EXIT_C:
    case SM_NO_SAVE_EXIT_C:
	retval = command;
	break;

    default:
	break;
    }

    if (!retval)
	sm_setup_edit(SM_BASE_WINDOW,
		      hit_scr,
		      SM_QUIT_C,
		      NULL,
		      hit_skey,
		      NULL);
    return(retval);
}

static int build_find_line ARG2(CHAR *, buf, SM_SCREEN, screen)
{
    CHAR	*value;
    CHAR	*exclude_fieldname = NULL;
    int		whoring = 0, flag = 0, nflag, i;
    INDEX	size;
#define NUM_EXCLUDE_FIELDS 3

    value = sm_fld_get(screen, "or");
    if (value && (*value == 'Y'))
	whoring = 1;

    strcpy(buf, "find ");

    value = sm_fld_get(screen, "root");
    value = sm_trim(value);
    if (!value || !*value) {
	sm_set_field(sm_fld_open(screen, "root"));
	sm_message("You must give a directory to start the search");
	return(1);
    }
    strcat(buf, value);
    strcat(buf, " ");

    SM_ASSIGN(exclude_fieldname, "excludeN");
    for (i = 0; i < NUM_EXCLUDE_FIELDS; i++) {
	*(SM_LASTCHAR(exclude_fieldname)) = '1' + i;
	value = sm_fld_get(screen, exclude_fieldname);
	value = sm_trim(value);
	if (value && *value) {
	    strcat(buf, "-name ");
	    strcat(buf, value);
	    strcat(buf, " -prune -o ");
	}
    }

    strcat(buf, "-type f ");
    if (whoring)
	strcat(buf, "\\( ");

    value = sm_fld_get(screen, "pattern");
    nflag = 0;
    if (*value == '!') {
	value++;
	nflag = 1;
    }
    if (value && *value) {
	if (flag && whoring)
	    strcat(buf, "-o ");
	if (nflag)
	    strcat(buf, "\\! ");
	buf = strchr(buf, 0);
	sprintf(buf, "-name '%s' ", value);
	flag = 1;
    }

    value = sm_fld_get(screen, "bigger");
    value = sm_trim(value);
    nflag = 0;
    if (*value == '!') {
	value++;
	nflag = 1;
    }
    if (value && *value) {
	size = AsciiToIndex(value);
	if (size > (INDEX) 0) {
	    if (flag && whoring)
		strcat(buf, "-o ");
	    if (nflag)
		strcat(buf, "\\! ");
	    if (toupper(*(SM_LASTCHAR(value))) == 'K')
		size *= 1024;
	    if (toupper(*(SM_LASTCHAR(value))) == 'M')
		size *= 1024*1024;
	    size = size / 512;
	    buf = strchr(buf, 0);
	    sprintf(buf, "-size +%ld ", (long) size);
	    flag = 1;
	}
    }

    value = sm_fld_get(screen, "inode");
    value = sm_trim(value);
    nflag = 0;
    if (*value == '!') {
	value++;
	nflag = 1;
    }
    if (value && *value) {
	if (AsciiToIndex(value) > 0L) {
	    if (flag && whoring)
		strcat(buf, "-o ");
	    if (nflag)
		strcat(buf, "\\! ");
	    buf = strchr(buf, 0);
	    sprintf(buf, "-inum %s ", value);
	    flag = 1;
	}
    }

    value = sm_fld_get(screen, "newer");
    value = sm_trim(value);
    nflag = 0;
    if (*value == '!') {
	value++;
	nflag = 1;
    }
    if (value && *value) {
	if (flag && whoring)
	    strcat(buf, "-o ");
	if (nflag)
	    strcat(buf, "\\! ");
	buf = strchr(buf, 0);
	sprintf(buf, "-newer %s ", value);
	flag = 1;
	buf = strchr(buf, 0);
    }

    value = sm_fld_get(screen, "accessed");
    value = sm_trim(value);
    nflag = 0;
    if (*value == '!') {
	value++;
	nflag = 1;
    }
    if (value && *value) {
	if (flag && whoring)
	    strcat(buf, "-o ");
	if (nflag)
	    strcat(buf, "\\! ");
	buf = strchr(buf, 0);
	sprintf(buf, "-atime -%s ", value);
	flag = 1;
	buf = strchr(buf, 0);
    }
    
    value = sm_fld_get(screen, "modified");
    value = sm_trim(value);
    nflag = 0;
    if (*value == '!') {
	value++;
	nflag = 1;
    }
    if (value && *value) {
	if (flag && whoring)
	    strcat(buf, "-o ");
	if (nflag)
	    strcat(buf, "\\! ");
	buf = strchr(buf, 0);
	sprintf(buf, "-ctime -%s ", value);
	flag = 1;
	buf = strchr(buf, 0);
    }

    if (whoring)
	strcat(buf, "\\) ");

    strcat(buf, "-print ");

    return(0);
}

static int do_it ARG1(SM_SCREEN, screen)
{
    CHAR	*command = alloca(SM_MAXREC);
    CHAR	*buf = command;
    CHAR	tmp[100];
    struct stat	statbuf;
    int		retval = 0;

    if (build_find_line(buf, screen))
	return(retval);
    buf = strchr(buf, 0);

    sm_mktemp(tmp, "TXXXXXX");
    sprintf(buf, " > %s", tmp);
    if (smio_system(command, 1) == 0) {
	if ((stat(tmp, &statbuf) != 0) ||
	    (statbuf.st_size == 0))
	    sm_message("No files matched");
	else {
	    smdb_delete_data(hit_scr);
	
	    /* read in results */
	    smdb_read_file(tmp, hit_scr, 1);
	    
	    sm_setup_scr(SM_BASE_WINDOW, 
			 find_scr, 
			 NULL,
			 NULL,
			 0,
			 0,
			 0);
	    sm_add_callback(do_hit, NULL);
	    sm_setup_edit(SM_BASE_WINDOW,
			  hit_scr,
			  SM_QUIT_C,
			  NULL,
			  hit_skey,
			  NULL);
	    retval = 1;
	}
    }

    /* Clean up */
    unlink(tmp);
    return(retval);
}

static int save_command ARG2(CHAR *, filename, SM_SCREEN, screen)
{
    CHAR	*buf = alloca(SM_MAXREC);
    FILE	*file;
    int		retval = 0;

    if (build_find_line(buf, screen))
	return(retval);
    
    if (backup_file(filename) < 0) {
	sprintf(buf, 
		"Can't make a backup file for \"%s\"", 
		filename);
	sm_message(buf);
    } 
    
    if ((file = fopen(filename, "w")) == NULL) {
	sm_file_error("Can't write to file: ", filename, errno);
	if (file)
	    fclose(file);
	retval = 1;
    } else {
	build_find_line(buf, screen);
	fputs(buf, file);
	fputc('\n', file);
	fclose(file);
	sprintf(buf, "Command line saved in \"%s\".", filename);
	sm_message(buf);
    }
    return(retval);
}

CALLBACK(do_save_command)
{
    FILE	*file;
    CHAR	*filename;

    /* Always entered after sm_prompt */
    if (command == SM_YES_C) {
	filename = sm_get_prompt_value();

	if ((file = fopen(filename, "r")) != NULL) {
	    CHAR	*buf = alloca(100);

	    fclose(file);
	    sprintf(buf, "\"%s\" already exists. Overwrite it?", filename);
	    if (sm_confirm(buf) != SM_YES_C)
		return(SM_QUIT_C);
	}
	save_command(filename, find_scr);
	command = SM_POPCALLBACK;
    }

    return(command);
}

static void setup_save_command()
{
    CHAR	pwd[MAXPATHLEN + 1];

    GETCWD(pwd, MAXPATHLEN);
    sm_add_callback(do_save_command, NULL);
    sm_setup_prompt("Enter file for the command line (current directory is:)", 
		    pwd, 
		    NULL,
		    NULL);
}

CALLBACK(smfind_main_loop)
{
    int			reset_change;
    CHAR		*buf = alloca(SM_MAXREC);
    
    switch(command) {
	
    case DO_IT:
#if 0
        /* this is too much optimisation - who knows? maybe someone
         * else created a file - just do the search that the user
         * asked for */
	if (sm_scr_changed(find_scr)) {
#endif
	    sm_set_scr_unchanged(find_scr);
	    if (do_it(find_scr))
		return(0);
#if 0
        } else {
	    sm_setup_scr(SM_BASE_WINDOW, 
			 find_scr, 
			 NULL,
			 NULL,
			 0,
			 0,
			 0);
	    sm_add_callback(do_hit, NULL);
	    sm_setup_edit(SM_BASE_WINDOW,
			  hit_scr,
			  SM_QUIT_C,
			  NULL,
			  hit_skey,
			  NULL);
	    return(0);
	}
#endif
	break;

    case WHORE:
	sm_fld_set(sm_fld_open(find_scr, "or"), 
		   sm_enq_skey(find_skey, "whore")? "Y": "N");
	break;

    case DISPLAY_COMMAND:
	if (build_find_line(buf, find_scr) == 0)
	    sm_message(buf);
	break;

    case SAVE_COMMAND:
	setup_save_command();
	return(0);

    case SM_EXIT_C:
    case SM_NO_SAVE_EXIT_C:
	return(command);
	break;

    default: /* Probably a return from one of the sub routines */
	break;
    }

    if (sm_scr_changed(find_scr))
	reset_change = 1;
    else
	reset_change = 0;
    sm_setup_scr(SM_BASE_WINDOW, 
		 find_scr, 
		 NULL,
		 NULL,
		 0,
		 0,
		 0);
    if (reset_change)
	sm_set_scr_changed(find_scr);
    return(0);
}

int main ARG2(int, argc, CHAR **, argv)
{
    int		c;
    SM_FILE	screenset;

    /* Temporary back door */
    if (setjmp(NoMoreHeap)) {
	alloca(0);
	sm_error_exit(sm_mem_error);
    }

    global.rev_string = "$Revision: 1.7 $";
    sm_pre_init(argc, argv);

    /* Get rid of X options and enter X if appropriate */
    sm_init(1, argc, argv);

    while ((c = getopt(argc, argv, "?")) != EOF)
        switch (c) {
        default:
            sm_term();
	    usage(); 
	    /* Unfortunately, if we've entered X in sm_init, then any error 
	       messages disappear */
	    break;
        }
    
#ifdef SM_DEBUG
    smio_flushinp();
#endif

    screenset = sm_scrmgr_screens();
#if READ_DAT_AT_RUNTIME
    {
	CHAR	buf[MAXPATHLEN + 1];

	buf[0] = 0;
#if NEEDS_PATH_FOR_PROGS
	strcpy(buf, sm_program_directory);
#endif
	strcat(buf, "smfind.dat");
	if (sm_fopen(screenset, buf) == SM_ERROR)
	    sm_error_exit("Error reading smfind.dat!");
    }
#else
    if (sm_dbopen(screenset, smfind_dat) == SM_ERROR)
	sm_error_exit("Error reading internal data block!");
#endif
#include "smfind.opn"

    /* Back door for when we run out of heap - horrible structure, but
       what can one do ?*/

    if (setjmp(NoMoreHeap)) {
	alloca(0);

	/* Prevent infinite loop */
	if (setjmp(NoMoreHeap)) {
	    alloca(0);
	    sm_error_exit(sm_mem_error);
	}

	sm_message(sm_mem_error);

	if (setjmp(NoMoreHeap)) {
	    /* Oh boy - really serious! */
	    alloca(0);
	    sm_error_exit("Ran out of heap - file(s) were not saved! Sorry!");
	}

	alloca(0);

	if (setjmp(NoMoreHeap) == 0)
	    sm_term();
	exit(1);
    }

    /*************/
    /* Main loop */
    /*************/

    sm_message(global.rev_string);

    sm_add_callback(smfind_main_loop, NULL);	
    sm_set_skey(find_skey, "whore", 0);
    sm_setup_scr(SM_BASE_WINDOW, 
		 find_scr, 
		 NULL,
		 NULL,
		 0,
		 0,
		 0);
    sm_set_scr_changed(find_scr);
    sm_main_loop();
    sm_term();
    exit(0);
}

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
